/*--------------------------------------------------------------------
 * bgworker.c
 *		POSTGRES 可插拔的后台工作进程实现
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/postmaster/bgworker.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/parallel.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "port/atomics.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/interrupt.h"
#include "postmaster/postmaster.h"
#include "replication/logicallauncher.h"
#include "replication/logicalworker.h"
#include "storage/dsm.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/lwlock.h"
#include "storage/pg_shmem.h"
#include "storage/pmsignal.h"
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/shmem.h"
#include "tcop/tcopprot.h"
#include "utils/ascii.h"
#include "utils/ps_status.h"
#include "utils/timeout.h"

/*
 * postmaster 注册的后台工作进程列表，位于私有内存中。
 */
slist_head	BackgroundWorkerList = SLIST_STATIC_INIT(BackgroundWorkerList);

/*
 * BackgroundWorkerSlots 存在于共享内存中，可以通过 BackgroundWorkerArray 被
 * postmaster 和普通的后端访问。 然而，postmaster 不能获取锁，包括自旋锁，因为这
 * 可能会导致它崩溃或者在共享内存损坏时卡住。 这样的结果是不可容忍的。因此，
 * 我们需要一个无锁协议来协调对这些数据的访问。
 *
 * 'in_use' 标志用于在 postmaster 和系统的其他部分之间交接插槽的责任。
 * 当 'in_use' 为 false 时，postmaster 将完全忽略该插槽，除了 'in_use' 
 * 标志本身，它可以读取。在这种状态下，普通后端可以修改该插槽。 一旦某个
 * 后端将 'in_use' 设置为 true，该插槽就成为 postmaster 的责任。普通后端
 * 不再可以修改它，但 postmaster 可以检查它。因此，初始化插槽的后端必须完
 * 全初始化插槽，并在标记为使用前插入一个写内存屏障。
 *
 * 作为例外，即使插槽正在使用中，普通后端仍然可以为插槽设置 'terminate' 
 * 标志，告知 postmaster 不要重启它。一旦后台工作进程不再运行，该插槽将
 * 被释放以供重用。
 *
 * 除了与 postmaster 协调外，修改此数据结构的后端还必须相互协调。由于它们
 * 可以获取锁，这一点很简单：任何希望操作插槽的后端必须以独占模式获取
 * BackgroundWorkerLock。希望读取可能被其他后端同时修改的数据的后端
 * 应该以共享模式获取此锁。无论如何，读取此数据结构的后端必须能够容忍
 * postmaster 的并发修改。
 */
typedef struct BackgroundWorkerSlot
{
	bool		in_use;
	bool		terminate;
	pid_t		pid;			/* InvalidPid = 尚未启动；0 = 死亡 */
	uint64		generation;		/* 当插槽被回收时递增 */
	BackgroundWorker worker;
} BackgroundWorkerSlot;

/*
 * 为了限制并行工作进程的总数（根据 max_parallel_workers GUC），
 * 我们维护活动并行工作进程的数量。由于 postmaster 不能获取锁，
 * 因此使用两个变量来实现这一目的：注册的并行工作进程数量（由
 * 后端修改，由 BackgroundWorkerLock 保护）和已终止的并行工作
 * 进程数量（仅由 postmaster 修改，且无锁）。活动并行工作进程的
 * 数量是注册工作进程数量减去已终止的数量。这些计数器当然可能会溢出，
 * 但在这里并不重要，因为减法仍然会给出正确的数字。
 */
typedef struct BackgroundWorkerArray
{
	int			total_slots;
	uint32		parallel_register_count;
	uint32		parallel_terminate_count;
	BackgroundWorkerSlot slot[FLEXIBLE_ARRAY_MEMBER];
} BackgroundWorkerArray;

struct BackgroundWorkerHandle
{
	int			slot;
	uint64		generation;
};

static BackgroundWorkerArray *BackgroundWorkerData;

/*
 * 内部后台工作进程入口点的列表。我们需要这个是因为在 LookupBackgroundWorkerFunction() 
 * 中解释的原因，如下所示。
 */
static const struct
{
	const char *fn_name;
	bgworker_main_type fn_addr;
}			InternalBGWorkers[] =

{
	{
		"ParallelWorkerMain", ParallelWorkerMain
	},
	{
		"ApplyLauncherMain", ApplyLauncherMain
	},
	{
		"ApplyWorkerMain", ApplyWorkerMain
	}
};

/* 私有函数。 */
static bgworker_main_type fc_LookupBackgroundWorkerFunction(const char *fc_libraryname, const char *fc_funcname);


/*
 * 计算所需的共享内存。
 */
Size BackgroundWorkerShmemSize(void)
{
	Size		fc_size;

	/* 工作进程的数组大小可变。 */
	fc_size = offsetof(BackgroundWorkerArray, slot);
	fc_size = add_size(fc_size, mul_size(max_worker_processes,
								   sizeof(BackgroundWorkerSlot)));

	return fc_size;
}

/*
 * 初始化共享内存。
 */
void BackgroundWorkerShmemInit(void)
{
	bool		fc_found;

	BackgroundWorkerData = ShmemInitStruct("Background Worker Data",
										   BackgroundWorkerShmemSize(),
										   &fc_found);
	if (!IsUnderPostmaster)
	{
		slist_iter	fc_siter;
		int			fc_slotno = 0;

		BackgroundWorkerData->total_slots = max_worker_processes;
		BackgroundWorkerData->parallel_register_count = 0;
		BackgroundWorkerData->parallel_terminate_count = 0;

		/*
		 * 将工作进程列表的内容复制到共享内存中。记录分配给每个
		 * 工作进程的共享内存插槽。这确保了 postmaster 的私有列表
		 * 与共享内存中的数组之间的一对一映射。
		 */
		slist_foreach(fc_siter, &BackgroundWorkerList)
		{
			BackgroundWorkerSlot *fc_slot = &BackgroundWorkerData->slot[fc_slotno];
			RegisteredBgWorker *fc_rw;

			fc_rw = slist_container(RegisteredBgWorker, rw_lnode, fc_siter.cur);
			Assert(fc_slotno < max_worker_processes);
			fc_slot->in_use = true;
			fc_slot->terminate = false;
			fc_slot->pid = InvalidPid;
			fc_slot->generation = 0;
			fc_rw->rw_shmem_slot = fc_slotno;
			fc_rw->rw_worker.bgw_notify_pid = 0;	/* 可能在崩溃后重新初始化 */
			memcpy(&fc_slot->worker, &fc_rw->rw_worker, sizeof(BackgroundWorker));
			++fc_slotno;
		}

		/*
		 * 将任何剩余插槽标记为未使用。
		 */
		while (fc_slotno < max_worker_processes)
		{
			BackgroundWorkerSlot *fc_slot = &BackgroundWorkerData->slot[fc_slotno];

			fc_slot->in_use = false;
			++fc_slotno;
		}
	}
	else
		Assert(fc_found);
}

/*
 * 在 postmaster 的后端私有注册背景工作进程对象列表中搜索
 * 与给定插槽编号对应的对象。
 */
static RegisteredBgWorker *
fc_FindRegisteredWorkerBySlotNumber(int fc_slotno)
{
	slist_iter	fc_siter;

	slist_foreach(fc_siter, &BackgroundWorkerList)
	{
		RegisteredBgWorker *fc_rw;

		fc_rw = slist_container(RegisteredBgWorker, rw_lnode, fc_siter.cur);
		if (fc_rw->rw_shmem_slot == fc_slotno)
			return fc_rw;
	}

	return NULL;
}

/*
 * 注意其他后端对共享内存的更改。
 * 仅在 allow_new_workers 为 true 时接受新的工作进程请求。
 *
 * 此代码在 postmaster 中运行，因此我们必须非常小心，不要假设
 * 共享内存的内容是合理的。否则，恶意后端可能会使 postmaster 
 * 崩溃。
 */
void BackgroundWorkerStateChange(bool fc_allow_new_workers)
{
	int			fc_slotno;

	/*
	 * 存储在共享内存中的槽位总数应该与我们对 max_worker_processes
	 * 的概念相匹配。如果不匹配，那么很可能出了问题。稍后，我们
	 * 将始终将此值称为 max_worker_processes，以防共享内存在循环
	 * 时损坏。
	 */
	if (max_worker_processes != BackgroundWorkerData->total_slots)
	{
		ereport(LOG,
				(errmsg("inconsistent background worker state (max_worker_processes=%d, total_slots=%d)",
						max_worker_processes,
						BackgroundWorkerData->total_slots)));
		return;
	}

	/*
	 * 遍历插槽，查找新注册的工作进程或必须终止的工作进程。
	 */
	for (fc_slotno = 0; fc_slotno < max_worker_processes; ++fc_slotno)
	{
		BackgroundWorkerSlot *fc_slot = &BackgroundWorkerData->slot[fc_slotno];
		RegisteredBgWorker *fc_rw;

		if (!fc_slot->in_use)
			continue;

		/*
		 * 确保在查看更新的插槽内容之前，不看到 in_use 标志。
		 */
		pg_read_barrier();

		/* 查看我们是否已经知道此工作进程。 */
		fc_rw = fc_FindRegisteredWorkerBySlotNumber(fc_slotno);
		if (fc_rw != NULL)
		{
			/*
			 * 一般来说，工作进程数据在最初注册后不能更改。
			 * 但是，有人可以设置 terminate 标志。
			 */
			if (fc_slot->terminate && !fc_rw->rw_terminate)
			{
				fc_rw->rw_terminate = true;
				if (fc_rw->rw_pid != 0)
					kill(fc_rw->rw_pid, SIGTERM);
				else
				{
					/* 报告从未启动、现在已终止的工作进程为死。 */
					ReportBackgroundWorkerPID(fc_rw);
				}
			}
			continue;
		}

		/*
		 * 如果我们不允许新的工作进程，那么立即将其标记为
		 * 终止；下一个阶段将处理清理工作。这样做可以确保任何
		 * 等待该工作进程的进程将被唤醒，即使该工作进程将不
		 * 会被允许运行。
		 */
		if (!fc_allow_new_workers)
			fc_slot->terminate = true;

		
/*
		 * 如果工作线程被标记为终止，我们不需要将其添加到
		 * 注册工作线程列表中；我们可以直接释放该槽位。 However, if
		 * bgw_notify_pid 被设置，则注册该工作线程的进程可能
		 * 需要知道我们已经处理了终止请求，所以一定要
		 * 发送信号。
		 */
		if (fc_slot->terminate)
		{
			int			fc_notify_pid;

			/*
			 * 我们在这里需要一个内存屏障，以确保 bgw_notify_pid
			 * 的加载和 parallel_terminate_count 的更新
			 * 在存储 in_use 之前完成。
			 */
			fc_notify_pid = fc_slot->worker.bgw_notify_pid;
			if ((fc_slot->worker.bgw_flags & BGWORKER_CLASS_PARALLEL) != 0)
				BackgroundWorkerData->parallel_terminate_count++;
			fc_slot->pid = 0;

			pg_memory_barrier();
			fc_slot->in_use = false;

			if (fc_notify_pid != 0)
				kill(fc_notify_pid, SIGUSR1);

			continue;
		}

		/*
		 * 将注册数据复制到注册工作线程列表中。
		 */
		fc_rw = malloc(sizeof(RegisteredBgWorker));
		if (fc_rw == NULL)
		{
			ereport(LOG,
					(errcode(ERRCODE_OUT_OF_MEMORY),
					 errmsg("out of memory")));
			return;
		}

		/*
		 * 以一种谨慎的方式复制字符串。如果共享内存被损坏，
		 * 源数据甚至可能没有以 NUL 结尾。
		 */
		ascii_safe_strlcpy(fc_rw->rw_worker.bgw_name,
						   fc_slot->worker.bgw_name, BGW_MAXLEN);
		ascii_safe_strlcpy(fc_rw->rw_worker.bgw_type,
						   fc_slot->worker.bgw_type, BGW_MAXLEN);
		ascii_safe_strlcpy(fc_rw->rw_worker.bgw_library_name,
						   fc_slot->worker.bgw_library_name, BGW_MAXLEN);
		ascii_safe_strlcpy(fc_rw->rw_worker.bgw_function_name,
						   fc_slot->worker.bgw_function_name, BGW_MAXLEN);

		/*
		 * 复制各种固定大小的字段。
		 *
		 * flags、start_time 和 restart_time 会被主进程检查，
		 * 但如果它们被损坏也不会发生太坏的事情。剩下的字段
		 * 只会被子进程检查。它可能会崩溃，但我们不会。
		 */
		fc_rw->rw_worker.bgw_flags = fc_slot->worker.bgw_flags;
		fc_rw->rw_worker.bgw_start_time = fc_slot->worker.bgw_start_time;
		fc_rw->rw_worker.bgw_restart_time = fc_slot->worker.bgw_restart_time;
		fc_rw->rw_worker.bgw_main_arg = fc_slot->worker.bgw_main_arg;
		memcpy(fc_rw->rw_worker.bgw_extra, fc_slot->worker.bgw_extra, BGW_EXTRALEN);

		/*
		 * 复制用于通知状态变化的 PID，但仅当主进程知道该 PID
		 * 的后端时。若主进程不知道该 PID，并不是错误，因为请求
		 * 工作线程的后端可能在这样做之后就已死掉（或被杀死）。
		 * 尽管如此，至少在我们统计这种情况的经验之前，要在相对
		 * 高的调试级别上记录一条消息。
		 */
		fc_rw->rw_worker.bgw_notify_pid = fc_slot->worker.bgw_notify_pid;
		if (!PostmasterMarkPIDForWorkerNotify(fc_rw->rw_worker.bgw_notify_pid))
		{
			elog(DEBUG1, "worker notification PID %ld is not valid",
				 (long) fc_rw->rw_worker.bgw_notify_pid);
			fc_rw->rw_worker.bgw_notify_pid = 0;
		}

		/* 初始化主进程的记账。 */
		fc_rw->rw_backend = NULL;
		fc_rw->rw_pid = 0;
		fc_rw->rw_child_slot = 0;
		fc_rw->rw_crashed_at = 0;
		fc_rw->rw_shmem_slot = fc_slotno;
		fc_rw->rw_terminate = false;

		/* Log it! */
		ereport(DEBUG1,
				(errmsg_internal("registering background worker \"%s\"",
								 fc_rw->rw_worker.bgw_name)));

		slist_push_head(&BackgroundWorkerList, &fc_rw->rw_lnode);
	}
}

/*
 * 忘记一个不再需要的后台工作线程。
 *
 * 必须通过传递一个指向它的 slist_mutable_iter 来识别该工作线程。
 * 这种约定允许在搜索工作线程列表时删除工作线程，并且避免
 * 再次搜索列表。
 *
 * 如果合适，调用者负责通知 bgw_notify_pid。
 *
 * 此函数只能在主进程中调用。
 */
void ForgetBackgroundWorker(slist_mutable_iter *fc_cur)
{
	RegisteredBgWorker *fc_rw;
	BackgroundWorkerSlot *fc_slot;

	fc_rw = slist_container(RegisteredBgWorker, rw_lnode, fc_cur->cur);

	Assert(fc_rw->rw_shmem_slot < max_worker_processes);
	fc_slot = &BackgroundWorkerData->slot[fc_rw->rw_shmem_slot];
	Assert(fc_slot->in_use);

	/*
	 * 我们在这里需要一个内存屏障，以确保 parallel_terminate_count 的更新
	 * 在存储 in_use 之前完成。
	 */
	if ((fc_rw->rw_worker.bgw_flags & BGWORKER_CLASS_PARALLEL) != 0)
		BackgroundWorkerData->parallel_terminate_count++;

	pg_memory_barrier();
	fc_slot->in_use = false;

	ereport(DEBUG1,
			(errmsg_internal("unregistering background worker \"%s\"",
							 fc_rw->rw_worker.bgw_name)));

	slist_delete_current(fc_cur);
	free(fc_rw);
}

/*
 * 在共享内存中报告一个新启动的后台工作线程的 PID。
 *
 * 此函数只能从主进程调用。
 */
void ReportBackgroundWorkerPID(RegisteredBgWorker *fc_rw)
{
	BackgroundWorkerSlot *fc_slot;

	Assert(fc_rw->rw_shmem_slot < max_worker_processes);
	fc_slot = &BackgroundWorkerData->slot[fc_rw->rw_shmem_slot];
	fc_slot->pid = fc_rw->rw_pid;

	if (fc_rw->rw_worker.bgw_notify_pid != 0)
		kill(fc_rw->rw_worker.bgw_notify_pid, SIGUSR1);
}

/*
 * 报告一个后台工作进程的 PID 现在为零，因为之前运行的后台工作进程已经退出。
 *
 * 该函数应仅从邮政主管调用。
 */
void ReportBackgroundWorkerExit(slist_mutable_iter *fc_cur)
{
	RegisteredBgWorker *fc_rw;
	BackgroundWorkerSlot *fc_slot;
	int			fc_notify_pid;

	fc_rw = slist_container(RegisteredBgWorker, rw_lnode, fc_cur->cur);

	Assert(fc_rw->rw_shmem_slot < max_worker_processes);
	fc_slot = &BackgroundWorkerData->slot[fc_rw->rw_shmem_slot];
	fc_slot->pid = fc_rw->rw_pid;
	fc_notify_pid = fc_rw->rw_worker.bgw_notify_pid;

	/*
	 * 如果这个工作进程被计划取消注册，则在通知启动它的进程之前执行该操作。
	 * 否则，如果该进程立即尝试重新使用该槽，则可能尚不可用。
	 * 理论上，如果该进程在错误的时刻检查 slot->pid，也可能发生这种情况，
	 * 但这样可以缩小窗口。
	 */
	if (fc_rw->rw_terminate ||
		fc_rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART)
		ForgetBackgroundWorker(fc_cur);

	if (fc_notify_pid != 0)
		kill(fc_notify_pid, SIGUSR1);
}

/*
 * 取消 SIGUSR1 以退出后台进程的 PID 的通知。
 *
 * 该函数应仅从邮政主管调用。
 */
void BackgroundWorkerStopNotifications(pid_t fc_pid)
{
	slist_iter	fc_siter;

	slist_foreach(fc_siter, &BackgroundWorkerList)
	{
		RegisteredBgWorker *fc_rw;

		fc_rw = slist_container(RegisteredBgWorker, rw_lnode, fc_siter.cur);
		if (fc_rw->rw_worker.bgw_notify_pid == fc_pid)
			fc_rw->rw_worker.bgw_notify_pid = 0;
	}
}

/*
 * 取消任何尚未开始的工作请求，它们有等待的进程。
 *
 * 这在正常的（“智能”或“快速”）数据库关闭过程中调用。
 * 在此之后，不会启动新的后台工作进程，因此任何可能在等待它们的请求
 * 需要被解除等待。我们通过完全取消 bgworker 注册来做到这一点，这或许
 * 有些过度，但由于我们正在关闭，注册记录是否保留无关紧要。
 *
 * 该函数应仅从邮政主管调用。
 */
void ForgetUnstartedBackgroundWorkers(void)
{
	slist_mutable_iter fc_iter;

	slist_foreach_modify(fc_iter, &BackgroundWorkerList)
	{
		RegisteredBgWorker *fc_rw;
		BackgroundWorkerSlot *fc_slot;

		fc_rw = slist_container(RegisteredBgWorker, rw_lnode, fc_iter.cur);
		Assert(fc_rw->rw_shmem_slot < max_worker_processes);
		fc_slot = &BackgroundWorkerData->slot[fc_rw->rw_shmem_slot];

		/* 如果尚未启动，并且有人在等待 ... */
		if (fc_slot->pid == InvalidPid &&
			fc_rw->rw_worker.bgw_notify_pid != 0)
		{
			/* ... 那就干掉它，并通知等待者 */
			int			fc_notify_pid = fc_rw->rw_worker.bgw_notify_pid;

			ForgetBackgroundWorker(&fc_iter);
			if (fc_notify_pid != 0)
				kill(fc_notify_pid, SIGUSR1);
		}
	}
}

/*
 * 重置后台工作进程崩溃状态。
 *
 * 我们假设，在一次崩溃和重启周期之后，不带有永不重启标志的后台工作进程
 * 应立即重启，而不是等待 bgw_restart_time 经过。另一方面，带有该标志的
 * 工作进程应立即被遗忘，因为我们将永远不会重新启动它们。
 *
 * 该函数应仅从邮政主管调用。
 */
void ResetBackgroundWorkerCrashTimes(void)
{
	slist_mutable_iter fc_iter;

	slist_foreach_modify(fc_iter, &BackgroundWorkerList)
	{
		RegisteredBgWorker *fc_rw;

		fc_rw = slist_container(RegisteredBgWorker, rw_lnode, fc_iter.cur);

		if (fc_rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART)
		{
			/*
			 * 标记为 BGW_NEVER_RESTART 的工作进程在崩溃后不应被重新启动，
			 * 所以忘记它们。 （如果我们等到崩溃后再忘记它们，而它们是并行工作进程，
			 * parallel_terminate_count 将在我们已经将 parallel_register_count 置为零之后
			 * 增加，这将是糟糕的。）
			 */
			ForgetBackgroundWorker(&fc_iter);
		}
		else
		{
			/*
			 * 我们通过 parallel_register_count 和 parallel_terminate_count 进行的
			 * 计算将被打乱，如果标记为并行的工作进程能够经过一次崩溃和重启周期。
			 * 所有此类工作进程都应标记为 BGW_NEVER_RESTART，因此控制
			 * 不应到达此分支。
			 */
			Assert((fc_rw->rw_worker.bgw_flags & BGWORKER_CLASS_PARALLEL) == 0);

			/*
			 * 允许此工作进程在完成重置后立即重新启动。
			 */
			fc_rw->rw_crashed_at = 0;

			/*
			 * 如果有人在等待它，他们就过去了。
			 */
			fc_rw->rw_worker.bgw_notify_pid = 0;
		}
	}
}

#ifdef EXEC_BACKEND
/*
 * 在 EXEC_BACKEND 模式下，工作进程使用此功能从共享内存检索其详细信息。
 */
BackgroundWorker *
BackgroundWorkerEntry(int fc_slotno)
{
	static BackgroundWorker fc_myEntry;
	BackgroundWorkerSlot *fc_slot;

	Assert(fc_slotno < BackgroundWorkerData->total_slots);
	fc_slot = &BackgroundWorkerData->slot[fc_slotno];
	Assert(fc_slot->in_use);

	/* 必须复制此内容，以防我们不打算保留共享内存访问权限 */
	memcpy(&fc_myEntry, &fc_slot->worker, sizeof fc_myEntry);
	return &fc_myEntry;
}
#endif

/*
 * 使用错误级别 elevel 抱怨 BackgroundWorker 的定义。
 * 如果看起来正常，则返回 true；否则返回 false（除非 elevel >= ERROR，
 * 在这种情况下我们在不正常的情况下根本不会返回）。
 */
static bool fc_SanityCheckBackgroundWorker(BackgroundWorker *fc_worker, int fc_elevel)
{
	/* 对标志进行完整性检查 */

	/*
	 * 我们曾经支持未连接到共享内存的工作进程，但现在不再支持。因此，
	 * 这现在是一个必需的标志。由于兼容性原因，我们不会删除该标志，
	 * 并且因为该标志在读取代码时仍提供某种信号。
	 */
	if (!(fc_worker->bgw_flags & BGWORKER_SHMEM_ACCESS))
	{
		ereport(fc_elevel,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("background worker \"%s\": background workers without shared memory access are not supported",
						fc_worker->bgw_name)));
		return false;
	}

	if (fc_worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION)
	{
		if (fc_worker->bgw_start_time == BgWorkerStart_PostmasterStart)
		{
			ereport(fc_elevel,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("background worker \"%s\": cannot request database access if starting at postmaster start",
							fc_worker->bgw_name)));
			return false;
		}

		/* XXX 需要其他检查吗？ */
	}

	if ((fc_worker->bgw_restart_time < 0 &&
		 fc_worker->bgw_restart_time != BGW_NEVER_RESTART) ||
		(fc_worker->bgw_restart_time > USECS_PER_DAY / 1000))
	{
		ereport(fc_elevel,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("background worker \"%s\": invalid restart interval",
						fc_worker->bgw_name)));
		return false;
	}

	/*
	 * 并行工作进程可能不被配置为重启，因为 parallel_register_count/parallel_terminate_count
	 * 的会计无法处理通过崩溃与重启周期的并行工作进程。
	 */
	if (fc_worker->bgw_restart_time != BGW_NEVER_RESTART &&
		(fc_worker->bgw_flags & BGWORKER_CLASS_PARALLEL) != 0)
	{
		ereport(fc_elevel,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("background worker \"%s\": parallel workers may not be configured for restart",
						fc_worker->bgw_name)));
		return false;
	}

	/*
	 * 如果 bgw_type 未填写，使用 bgw_name。
	 */
	if (strcmp(fc_worker->bgw_type, "") == 0)
		strcpy(fc_worker->bgw_type, fc_worker->bgw_name);

	return true;
}

/*
 * 后台工作进程的标准 SIGTERM 处理程序
 */
static void fc_bgworker_die(SIGNAL_ARGS)
{
	PG_SETMASK(&BlockSig);

	ereport(FATAL,
			(errcode(ERRCODE_ADMIN_SHUTDOWN),
			 errmsg("terminating background worker \"%s\" due to administrator command",
					MyBgworkerEntry->bgw_type)));
}

/*
 * 启动一个新的后台工作进程
 *
 * 这是后台工作进程的主要入口点，应从邮政主管调用。
 */
void StartBackgroundWorker(void)
{
	sigjmp_buf	fc_local_sigjmp_buf;
	BackgroundWorker *fc_worker = MyBgworkerEntry;
	bgworker_main_type fc_entrypt;

	if (fc_worker == NULL)
		elog(FATAL, "unable to find bgworker entry");

	IsBackgroundWorker = true;

	MyBackendType = B_BG_WORKER;
	init_ps_display(fc_worker->bgw_name);

	SetProcessingMode(InitProcessing);

	/* 应用 PostAuthDelay */
	if (PostAuthDelay > 0)
		pg_usleep(PostAuthDelay * 1000000L);

	/*
	 * 设置信号处理程序。
	 */
	if (fc_worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION)
	{
		/*
		 * SIGINT 用于表示取消当前操作
		 */
		pqsignal(SIGINT, StatementCancelHandler);
		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
		pqsignal(SIGFPE, FloatExceptionHandler);

		/* XXX 这里需要其他处理程序吗？ */
	}
	else
	{
		pqsignal(SIGINT, SIG_IGN);
		pqsignal(SIGUSR1, SIG_IGN);
		pqsignal(SIGFPE, SIG_IGN);
	}
	pqsignal(SIGTERM, fc_bgworker_die);
	/* SIGQUIT 处理程序已经由 InitPostmasterChild 设置 */
	pqsignal(SIGHUP, SIG_IGN);

	InitializeTimeouts();		/* 建立 SIGALRM 处理程序 */

	pqsignal(SIGPIPE, SIG_IGN);
	pqsignal(SIGUSR2, SIG_IGN);
	pqsignal(SIGCHLD, SIG_DFL);

	/*
	 * 如果遇到异常，处理将在此处恢复。
	 *
	 * 我们只需清理，报告错误，并离开。
	 */
	if (sigsetjmp(fc_local_sigjmp_buf, 1) != 0)
	{
		/* 由于未使用 PG_TRY，必须手动重置错误堆栈 */
		error_context_stack = NULL;

		/* 在清理期间防止中断 */
		HOLD_INTERRUPTS();

		/*
		 * sigsetjmp 将阻塞所有信号，但在与我们的并行领导者通信时，我们可能需要接受信号。
		 * 一旦我们完成 HOLD_INTERRUPTS()，解除信号阻塞应该是安全的。
		 */
		BackgroundWorkerUnblockSignals();

		/* 向并行领导者和服务器日志报告错误 */
		EmitErrorReport();

		/*
		 * 这里还需要更多清理吗？对于与共享内存连接的 bgworkers，我们
		 * 将下面调用 InitProcess，它将安装 ProcKill 作为退出回调。
		 * 这将处理释放锁等操作。
		 */

		/* 然后离开 */
		proc_exit(1);
	}

	/* 我们现在可以处理 ereport(ERROR) */
	PG_exception_stack = &fc_local_sigjmp_buf;

	/*
	 * 在共享内存中创建每个后端的 PGPROC 结构，EXEC_BACKEND 案例中
	 * 除外，此时在 SubPostmasterMain 中已经完成此操作。在我们可以使用
	 * LWLocks 之前必须执行此操作（在 EXEC_BACKEND 案例中，我们已经
	 * 必须处理 LWLocks）。
	 */
#ifndef EXEC_BACKEND
	InitProcess();
#endif

	/*
	 * 早期初始化。
	 */
	BaseInit();

	/*
	 * 查找入口函数，如果必要的话加载它的库。
	 */
	fc_entrypt = fc_LookupBackgroundWorkerFunction(fc_worker->bgw_library_name,
											 fc_worker->bgw_function_name);

	/*
	 * 请注意，在正常进程中，我们会在这里调用 InitPostgres。然而，对于一个工作进程，我们尚不知道要连接哪个数据库；因此，我们需要等到用户代码通过
	 * BackgroundWorkerInitializeConnection() 来完成这件事。
	 */

	/*
	 * 现在调用用户定义的工作代码
	 */
	fc_entrypt(fc_worker->bgw_main_arg);

	/* ... 如果它返回，我们就完成了 */
	proc_exit(0);
}

/*
 * 注册一个新的静态后台工作进程。
 *
 * 这只能直接从 postmaster 或在由 shared_preload_libraries 加载的模块库的 _PG_init 函数中调用；
 * 否则将无效。
 */
void RegisterBackgroundWorker(BackgroundWorker *fc_worker)
{
	RegisteredBgWorker *fc_rw;
	static int	fc_numworkers = 0;

	if (!IsUnderPostmaster)
		ereport(DEBUG1,
				(errmsg_internal("registering background worker \"%s\"", fc_worker->bgw_name)));

	if (!process_shared_preload_libraries_in_progress &&
		strcmp(fc_worker->bgw_library_name, "postgres") != 0)
	{
		if (!IsUnderPostmaster)
			ereport(LOG,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("background worker \"%s\": must be registered in shared_preload_libraries",
							fc_worker->bgw_name)));
		return;
	}

	if (!fc_SanityCheckBackgroundWorker(fc_worker, LOG))
		return;

	if (fc_worker->bgw_notify_pid != 0)
	{
		ereport(LOG,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("background worker \"%s\": only dynamic background workers can request notification",
						fc_worker->bgw_name)));
		return;
	}

	/*
	 * 强制最大工作进程数。请注意，这过于严格：我们可以允许更多的非共享内存连接工作进程，因为这些并不计入其他地方的 MAX_BACKENDS 限制。
	 * 目前，放宽这一限制似乎并不重要。
	 */
	if (++fc_numworkers > max_worker_processes)
	{
		ereport(LOG,
				(errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
				 errmsg("too many background workers"),
				 errdetail_plural("Up to %d background worker can be registered with the current settings.",
								  "Up to %d background workers can be registered with the current settings.",
								  max_worker_processes,
								  max_worker_processes),
				 errhint("Consider increasing the configuration parameter \"max_worker_processes\".")));
		return;
	}

	/*
	 * 将注册数据复制到已注册工作进程列表中。
	 */
	fc_rw = malloc(sizeof(RegisteredBgWorker));
	if (fc_rw == NULL)
	{
		ereport(LOG,
				(errcode(ERRCODE_OUT_OF_MEMORY),
				 errmsg("out of memory")));
		return;
	}

	fc_rw->rw_worker = *fc_worker;
	fc_rw->rw_backend = NULL;
	fc_rw->rw_pid = 0;
	fc_rw->rw_child_slot = 0;
	fc_rw->rw_crashed_at = 0;
	fc_rw->rw_terminate = false;

	slist_push_head(&BackgroundWorkerList, &fc_rw->rw_lnode);
}

/*
 * 从常规后端注册一个新的后台工作进程。
 *
 * 成功时返回真，失败时返回假。失败通常表示当前没有可用的后台工作进程槽位。
 *
 * 如果 handle != NULL，我们将设置 *handle 为一个可以随后作为参数传递给 GetBackgroundWorkerPid() 的指针。
 * 调用者可以使用 pfree() 释放这个指针（如果需要）。
 */
bool RegisterDynamicBackgroundWorker(BackgroundWorker *fc_worker,
								BackgroundWorkerHandle **fc_handle)
{
	int			fc_slotno;
	bool		fc_success = false;
	bool		fc_parallel;
	uint64		fc_generation = 0;

	/*
	 * 我们无法从 postmaster 注册动态后台工作进程。如果这是一个独立的后端，我们就是唯一的进程，无法再启动更多。
	 * 在多进程环境中，理论上可能，但由于锁的考虑，我们目前不支持它；请参见关于 BackgroundWorkerSlot 数据
	 * 结构的注释。
	 */
	if (!IsUnderPostmaster)
		return false;

	if (!fc_SanityCheckBackgroundWorker(fc_worker, ERROR))
		return false;

	fc_parallel = (fc_worker->bgw_flags & BGWORKER_CLASS_PARALLEL) != 0;

	LWLockAcquire(BackgroundWorkerLock, LW_EXCLUSIVE);

	/*
	 * 如果这是一个并行工作进程，请检查是否已经有太多并行工作进程；如果是，请不要注册另一个。我们的 parallel_terminate_count 视图可能会稍显过时，
	 * 但这并不重要：如果我们稍早到达这里，结果也会是一样的。此外，由于 postmaster 不能获取锁，因此也没有办法可言，内存屏障无法保证任何有用的内容。
	 */
	if (fc_parallel && (BackgroundWorkerData->parallel_register_count -
					 BackgroundWorkerData->parallel_terminate_count) >=
		max_parallel_workers)
	{
		Assert(BackgroundWorkerData->parallel_register_count -
			   BackgroundWorkerData->parallel_terminate_count <=
			   MAX_PARALLEL_WORKER_LIMIT);
		LWLockRelease(BackgroundWorkerLock);
		return false;
	}

	/*
	 * 查找一个未使用的槽位。如果找到一个，就占用它。
	 */
	for (fc_slotno = 0; fc_slotno < BackgroundWorkerData->total_slots; ++fc_slotno)
	{
		BackgroundWorkerSlot *fc_slot = &BackgroundWorkerData->slot[fc_slotno];

		if (!fc_slot->in_use)
		{
			memcpy(&fc_slot->worker, fc_worker, sizeof(BackgroundWorker));
			fc_slot->pid = InvalidPid; /* 表示尚未启动 */
			fc_slot->generation++;
			fc_slot->terminate = false;
			fc_generation = fc_slot->generation;
			if (fc_parallel)
				BackgroundWorkerData->parallel_register_count++;

			/*
			 * 确保 postmaster 在看到新内容之前不会将槽位视为正在使用。
			 */
			pg_write_barrier();

			fc_slot->in_use = true;
			fc_success = true;
			break;
		}
	}

	LWLockRelease(BackgroundWorkerLock);

	/* 如果找到了槽位，告诉 postmaster 注意到变更。 */
	if (fc_success)
		SendPostmasterSignal(PMSIGNAL_BACKGROUND_WORKER_CHANGE);

	/*
	 * 如果我们找到了槽位并且用户提供了句柄，就初始化它。
	 */
	if (fc_success && fc_handle)
	{
		*fc_handle = palloc(sizeof(BackgroundWorkerHandle));
		(*fc_handle)->slot = fc_slotno;
		(*fc_handle)->generation = fc_generation;
	}

	return fc_success;
}

/*
 * 获取动态注册后台工作进程的 PID。
 *
 * 如果确定工作进程正在运行，返回值将为 BGWH_STARTED，并且 *pidp 将获取工作进程的 PID。
 * 如果 postmaster 尚未尝试启动工作进程，返回值将为 BGWH_NOT_YET_STARTED。否则，返回值为 BGWH_STOPPED。
 *
 * BGWH_STOPPED 可能表示工作进程暂时停止（因为它配置为自动重启并且以非零值退出），
 * 或者表示工作进程永久停止（因为它以退出代码 0 退出，或未配置为自动重启），
 * 甚至表示工作进程在从未启动的情况下被注销（因为启动失败且工作进程未配置为自动重启，或因为
 * 在工作进程成功启动之前使用了 TerminateBackgroundWorker）。
 */
BgwHandleStatus GetBackgroundWorkerPid(BackgroundWorkerHandle *fc_handle, pid_t *fc_pidp)
{
	BackgroundWorkerSlot *fc_slot;
	pid_t		fc_pid;

	Assert(fc_handle->slot < max_worker_processes);
	fc_slot = &BackgroundWorkerData->slot[fc_handle->slot];

	/*
	 * 我们可能可以安排仅使用内存屏障来同步对数据的访问，但目前，我们还是保持简单并获取锁。这里的流量不足以
	 * 导致有意义的争用，这似乎不太可能。
	 */
	LWLockAcquire(BackgroundWorkerLock, LW_SHARED);

	/*
	 * 在我们持有锁时，代数编号不能被并发修改。pid 由 postmaster 更新，可以随时更改，但我们假设这种变化是原子的。
	 * 因此，我们读取的值不会是垃圾，但在调用者检查它时可能会过时（不过这无论如何都是不可避免的）。
	 *
	 * in_use 标志可能正在从 true 改变为 false，但如果它已经是 false，则不能再进一步改变。
	 */
	if (fc_handle->generation != fc_slot->generation || !fc_slot->in_use)
		fc_pid = 0;
	else
		fc_pid = fc_slot->pid;

	/* 完成。 */
	LWLockRelease(BackgroundWorkerLock);

	if (fc_pid == 0)
		return BGWH_STOPPED;
	else if (fc_pid == InvalidPid)
		return BGWH_NOT_YET_STARTED;
	*fc_pidp = fc_pid;
	return BGWH_STARTED;
}


/*
 * 等待后台工作进程启动。
 *
 * 这类似于 GetBackgroundWorkerPid()，除了如果工作进程尚未
 * 启动，我们将等待它启动；因此，BGWH_NOT_YET_STARTED 永远
 * 不会被返回。然而，如果主进程已经死亡，我们就放弃并返回
 * BGWH_POSTMASTER_DIED，因为在这种情况下我们知道启动将不会
 * 发生。
 *
 * 调用者 *必须* 将我们的 PID 设置为工作进程的 bgw_notify_pid，
 * 否则我们在工作进程状态改变时将不会被及时唤醒。
 */
BgwHandleStatus WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *fc_handle, pid_t *fc_pidp)
{
	BgwHandleStatus fc_status;
	int			fc_rc;

	for (;;)
	{
		pid_t		fc_pid;

		CHECK_FOR_INTERRUPTS();

		fc_status = GetBackgroundWorkerPid(fc_handle, &fc_pid);
		if (fc_status == BGWH_STARTED)
			*fc_pidp = fc_pid;
		if (fc_status != BGWH_NOT_YET_STARTED)
			break;

		fc_rc = WaitLatch(MyLatch,
					   WL_LATCH_SET | WL_POSTMASTER_DEATH, 0,
					   WAIT_EVENT_BGWORKER_STARTUP);

		if (fc_rc & WL_POSTMASTER_DEATH)
		{
			fc_status = BGWH_POSTMASTER_DIED;
			break;
		}

		ResetLatch(MyLatch);
	}

	return fc_status;
}

/*
 * 等待后台工作进程停止。
 *
 * 如果工作进程尚未启动，或者正在运行，我们将等待它停止
 * 然后返回 BGWH_STOPPED。然而，如果主进程已经死亡，我们放弃
 * 并返回 BGWH_POSTMASTER_DIED，因为是主进程在工作进程状态
 * 变化时通知我们的。
 *
 * 调用者 *必须* 将我们的 PID 设置为工作进程的 bgw_notify_pid，
 * 否则我们在工作进程状态改变时将不会被及时唤醒。
 */
BgwHandleStatus WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *fc_handle)
{
	BgwHandleStatus fc_status;
	int			fc_rc;

	for (;;)
	{
		pid_t		fc_pid;

		CHECK_FOR_INTERRUPTS();

		fc_status = GetBackgroundWorkerPid(fc_handle, &fc_pid);
		if (fc_status == BGWH_STOPPED)
			break;

		fc_rc = WaitLatch(MyLatch,
					   WL_LATCH_SET | WL_POSTMASTER_DEATH, 0,
					   WAIT_EVENT_BGWORKER_SHUTDOWN);

		if (fc_rc & WL_POSTMASTER_DEATH)
		{
			fc_status = BGWH_POSTMASTER_DIED;
			break;
		}

		ResetLatch(MyLatch);
	}

	return fc_status;
}

/*
 * 指示主进程终止一个后台工作进程。
 *
 * 请注意，无论工作进程是否仍在运行，甚至是否已经退出并被
 * 注销，执行此操作都是安全的。
 */
void TerminateBackgroundWorker(BackgroundWorkerHandle *fc_handle)
{
	BackgroundWorkerSlot *fc_slot;
	bool		fc_signal_postmaster = false;

	Assert(fc_handle->slot < max_worker_processes);
	fc_slot = &BackgroundWorkerData->slot[fc_handle->slot];

	/* 在共享内存中设置终止标志，除非槽位已被重用。 */
	LWLockAcquire(BackgroundWorkerLock, LW_EXCLUSIVE);
	if (fc_handle->generation == fc_slot->generation)
	{
		fc_slot->terminate = true;
		fc_signal_postmaster = true;
	}
	LWLockRelease(BackgroundWorkerLock);

	/* 确保主进程注意到共享内存的变化。 */
	if (fc_signal_postmaster)
		SendPostmasterSignal(PMSIGNAL_BACKGROUND_WORKER_CHANGE);
}

/*
 * 查找（并可能加载）一个 bgworker 入口点函数。
 *
 * 对于包含在核心代码中的函数，我们使用库名称 "postgres"
 * 并参考 InternalBGWorkers 数组。外部函数通过使用 
 * load_external_function() 查找并在必要时加载。
 *
 * 这样做的目的是在进程边界之间传递函数名称作为字符串。
 * 我们不能传递实际的函数地址，因为存在函数已在不同进程中
 * 加载到不同地址的可能性。对于可加载库中的函数，这显然是
 * 一个危险，但即使是核心代码中的函数在使用 EXEC_BACKEND
 * 的平台上（例如，Windows）也可能发生这种情形。
 *
 * 在某些时候，摆脱 InternalBGWorkers[]，以支持对核心函数应用
 * load_external_function() 可能是值得的；但这引发了不值得
 * 现在解决的可移植性问题。
 */
static bgworker_main_type
fc_LookupBackgroundWorkerFunction(const char *fc_libraryname, const char *fc_funcname)
{
	/*
	 * 如果函数来自 postgres 本身，搜索 InternalBGWorkers 数组。
	 */
	if (strcmp(fc_libraryname, "postgres") == 0)
	{
		int			fc_i;

		for (fc_i = 0; fc_i < lengthof(InternalBGWorkers); fc_i++)
		{
			if (strcmp(InternalBGWorkers[fc_i].fn_name, fc_funcname) == 0)
				return InternalBGWorkers[fc_i].fn_addr;
		}

		/* 我们只能通过编程错误达到这一点。 */
		elog(ERROR, "internal function \"%s\" not found", fc_funcname);
	}

	/* 否则从外部库加载。 */
	return (bgworker_main_type)
		load_external_function(fc_libraryname, fc_funcname, true, NULL);
}

/*
 * 给定一个 PID，获取后台工作进程的 bgw_type。如果不是有效的
 * 背景工作进程则返回 NULL。
 *
 * 返回值位于属于此函数的静态内存中，因此必须在再次调用
 * 此函数之前使用。这样做是为了让调用者不必担心后台工作
 * 进程的锁定协议。
 */
const char * GetBackgroundWorkerTypeByPid(pid_t fc_pid)
{
	int			fc_slotno;
	bool		fc_found = false;
	static char fc_result[BGW_MAXLEN];

	LWLockAcquire(BackgroundWorkerLock, LW_SHARED);

	for (fc_slotno = 0; fc_slotno < BackgroundWorkerData->total_slots; fc_slotno++)
	{
		BackgroundWorkerSlot *fc_slot = &BackgroundWorkerData->slot[fc_slotno];

		if (fc_slot->pid > 0 && fc_slot->pid == fc_pid)
		{
			strcpy(fc_result, fc_slot->worker.bgw_type);
			fc_found = true;
			break;
		}
	}

	LWLockRelease(BackgroundWorkerLock);

	if (!fc_found)
		return NULL;

	return fc_result;
}
